package com.hunter.lucene.util.index;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Set;
import java.util.Map.Entry;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;

import com.hunter.lucene.util.config.AnalyzerHolder;
import com.hunter.lucene.util.config.DocumentConfigInfo;
import com.hunter.lucene.util.config.FieldableConfigInfo;
import com.hunter.lucene.util.config.MappingConfigInfoHolder.ClassKeyHolder;
import com.hunter.lucene.util.config.MappingConfigInfoHolder.MapHolder;
import com.hunter.lucene.util.convert.FieldConvertor;
import com.hunter.lucene.util.index.build.BuildDocumentException;
import com.hunter.lucene.util.index.build.DocumentBuilder;
import com.hunter.lucene.util.index.build.SimpleDocumentBuilder;

/**
 * 此类的具体索引的抽象类
 * 
 * @author bastengao
 * 
 */
public class Writer implements Writable {

	private IndexWriter indexWriter;
	private AnalyzerHolder analyzers = null;
	private MapHolder<Class<?>, DocumentConfigInfo> documentMappings = null;
	private MapHolder<Class<?>, DocumentBuilder> builders = new ClassKeyHolder<DocumentBuilder>();
	private MapHolder<Class<?>, FieldConvertor> convertors = new ClassKeyHolder<FieldConvertor>();

	public Writer(IndexWriter indexWriter, AnalyzerHolder analyzers,
			MapHolder<Class<?>, DocumentConfigInfo> documentMappings) {
		super();
		this.indexWriter = indexWriter;
		this.analyzers = analyzers;
		this.documentMappings = documentMappings;

		init();
	}

	private void init() {
		indexWriter.setInfoStream(System.out);
		initDocumentBuilder();
	}

	private void initDocumentBuilder() {
		Set<Entry<Class<?>, DocumentConfigInfo>> entries = documentMappings
				.entrySet();
		for (Entry<Class<?>, DocumentConfigInfo> entry : entries) {
			DocumentBuilder documentBuilder = new SimpleDocumentBuilder(entry
					.getValue(), convertors);
			builders.put(entry.getKey(), documentBuilder);
		}
	}

	@Override
	public void add(Object document) throws IndexException {
		isSupported(document);

		Class<?> clazz = document.getClass();
		DocumentConfigInfo documentInfo = documentMappings.get(clazz);
		DocumentBuilder docBuilder = builders.get(clazz);
		Document doc = null;
		try {
			doc = docBuilder.build(document);
		} catch (BuildDocumentException e) {
			throw new IndexException(e);
		}

		String analyzerName = documentInfo.analyzerName();
		String defaultAnalyzerName = analyzers.getDefaultAnalyzerName();
		if (defaultAnalyzerName.equals(analyzerName)) {
			try {
				indexWriter.addDocument(doc);
			} catch (CorruptIndexException e) {
				throw new IndexException(e);
			} catch (IOException e) {
				throw new IndexException(e);
			}
		} else {
			Analyzer analyzer = analyzers.get(analyzerName);
			try {
				indexWriter.addDocument(doc, analyzer);
			} catch (CorruptIndexException e) {
				throw new IndexException(e);
			} catch (IOException e) {
				throw new IndexException(e);
			}
		}

	}

	@Override
	public void addOrUpdate(Object document) throws IndexException {
		update(document);
	}

	@Override
	public void remove(Object document) throws IndexException {
		isSupported(document);
		isModifiable(document);

		Term term = null;
		try {
			term = uniqueFieldTerm(document);
		} catch (IllegalArgumentException e) {
			throw new IndexException(e);
		} catch (IllegalAccessException e) {
			throw new IndexException(e);
		} catch (InvocationTargetException e) {
			throw new IndexException(e);
		}

		try {
			indexWriter.deleteDocuments(term);
		} catch (CorruptIndexException e) {
			throw new IndexException(e);
		} catch (IOException e) {
			throw new IndexException(e);
		}

	}

	@Override
	public void update(Object document) throws IndexException {
		isSupported(document);
		isModifiable(document);

		Term term = null;
		try {
			term = uniqueFieldTerm(document);
		} catch (IllegalArgumentException e) {
			throw new IndexException(e);
		} catch (IllegalAccessException e) {
			throw new IndexException(e);
		} catch (InvocationTargetException e) {
			throw new IndexException(e);
		}

		Class<?> clazz = document.getClass();
		DocumentConfigInfo documentInfo = documentMappings.get(clazz);
		DocumentBuilder docBuilder = builders.get(clazz);
		Document doc = null;
		try {
			doc = docBuilder.build(document);
		} catch (BuildDocumentException e) {
			throw new IndexException(e);
		}

		String analyzerName = documentInfo.analyzerName();
		String defaultAnalyzerName = analyzers.getDefaultAnalyzerName();
		if (defaultAnalyzerName.equals(analyzerName)) {
			try {
				indexWriter.updateDocument(term, doc);
			} catch (CorruptIndexException e) {
				throw new IndexException(e);
			} catch (IOException e) {
				throw new IndexException(e);
			}
		} else {
			Analyzer analyzer = analyzers.get(analyzerName);
			try {
				indexWriter.updateDocument(term, doc, analyzer);
			} catch (CorruptIndexException e) {
				throw new IndexException(e);
			} catch (IOException e) {
				throw new IndexException(e);
			}
		}
	}

	/**
	 * 判断该对想是否是被支持的。
	 * 
	 * @param document
	 * @throws IndexException
	 */
	private void isSupported(Object document) throws IndexException {
		Class<?> clazz = document.getClass();
		DocumentConfigInfo configInfo = documentMappings.get(clazz);
		if (configInfo == null) {
			throw new IndexException("this type is not supported "+clazz);
		}
	}

	/**
	 * 判断该对象的修改(update,remove)操作是否是被支持的。
	 * 
	 * @param document
	 */
	private void isModifiable(Object document) {
		DocumentConfigInfo documentInfo = documentMappings.get(document
				.getClass());
		FieldableConfigInfo fieldInfo = documentInfo.uniqueField();
		if (fieldInfo == null) {
			throw new UnsupportedOperationException(
					"this object has not a unique field");
		}
	}

	private Term uniqueFieldTerm(Object document)
			throws IllegalArgumentException, IllegalAccessException,
			InvocationTargetException {
		DocumentConfigInfo documentInfo = documentMappings.get(document
				.getClass());

		FieldableConfigInfo fieldInfo = documentInfo.uniqueField();

		String fieldName = fieldInfo.name();

		Object returnValue = fieldInfo.readMethod().invoke(document);

		FieldConvertor convertor = convertors.get(fieldInfo.convertor());

		if (convertor == null) {
			try {
				convertor = fieldInfo.convertor().newInstance();
				convertors.put(fieldInfo.convertor(), convertor);
			} catch (InstantiationException e) {
				e.printStackTrace();
			}
		}

		String value = convertor.toStringValue(returnValue);

		return new Term(fieldName, value);

	}

	@Override
	public void commit() throws IndexException {
		try {
			indexWriter.commit();
		} catch (CorruptIndexException e) {
			throw new IndexException(e);
		} catch (IOException e) {
			throw new IndexException(e);
		}
	}

	@Override
	public void rollback() throws IndexException {
		try {
			indexWriter.rollback();
		} catch (IOException e) {
			throw new IndexException(e);
		}
	}

	@Override
	public void close() throws CorruptIndexException, IOException {
		try {
			indexWriter.optimize();
		} catch (CorruptIndexException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		indexWriter.close();
	}

}
